<?php
namespace Utuo\Pay\Driver;

/**
 * 微信支付v3.3.6
 */
class Wxtenpay extends \Utuo\Pay\Pay {
	
	private $prePay = "https://api.mch.weixin.qq.com/pay/unifiedorder"; // 统一支付接口,可接受 JSAPI/NATIVE/APP下预支付订单，返回预支付订单号
	private $refundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund"; // 退款申请接口
	private $refundSearchUrl = "https://api.mch.weixin.qq.com/pay/refundquery"; // 退款查询接口
	
	protected $config = array(
		'appid' => '',
		'appsecret' => '',
		'Mchid' => '',
		'Key' => '',
	);
	
	/**
	 * 验证配置
	 * @see \Utuo\Pay\Pay::check()
	 */
	public function check() {
		if (!$this->config['Mchid'] || !$this->config['Key']) {
			E("微信支付设置有误！");
		}
		return true;
	}
	
	/**
	 * 创建支付表单
	 * @param object
	 * {
	 *   ... ...
	 * 	 'param' => array(
	 * 		'isReady' => 0, // 微浏览器是否已加载页面 0.否 1.是
	 *    )
	 * }
	 * @see \Utuo\Pay\Pay::buildRequestForm()
	 */
	public function buildRequestForm( \Utuo\Pay\PayVo $vo ) {
		$data['return_code'] = 'FAIL';
		// 提交订单给微信
		$data = $this->submitOrder( $vo );
		if( $data ) {
			if ( $data['return_code'] == 'SUCCESS' ) {
				if ( $data['result_code'] == 'SUCCESS' ) {
					// 创建表单参数
					$params = array(
							'appId' => $this->config['appid'],
							'timeStamp' => time(),
							'nonceStr' => md5(time().rand(9999, 9999)),
							'signType' => 'MD5',
							'package' => "prepay_id={$data['prepay_id']}", );
					$params['paySign'] = $this->createSign($params, $this->config['Key'] );
					// 创建表单附加参数
					$wparams = $vo->getParam();
					if ( $wparams ) {
						if ( $wparams['isReady'] ) {
							$params['_isReady'] = 1;
						} 
					}
					return $this->_buildForm( $params );
				} else {
					return "result_msg:".$data['result_msg']."<br/> err_code:".$data['err_code']."<br/> err_code_des:".$data['err_code_des'];
				}
			}  else {
				return "return_msg:".$data['return_msg'];
			}
		}
		return '订单提交失败';
	}
	
	/**
	 * 退款申请
	 * @param string $transactionId 微信订单号 ($transactionId,orderNo二选一) transactionId > orderNo
	 * @param string $outRefundNo 商户系统内部的退款单号(*)
	 * @param real $totalFee 订单总金额(*) 
	 * @param real $refundFee 退款总金额(*)
	 * @param array $wparams 附加参数 
	 * array(
	 *   'orderNo' // 订单号(*)
	 * )
	 * @return array
	 * array(
	 * 	  'couponRefundFee', // 现金券退款金额 
	 * )
	 */
	public function refund( $transactionId = '', $outRefundNo = '', $totalFee = 0.00, $refundFee = 0.00, $wparams  ) {
		$params = array('appid' => $this->config['appid'],
				'mch_id' => $this->config['Mchid'],
				'nonce_str' => md5(time().rand(9999, 9999)),
				'out_trade_no' => $wparams['orderNo'],
				'out_refund_no' => $outRefundNo,
				'total_fee' => floor($totalFee * 100), 
				'refund_fee' => floor($refundFee * 100),
				'op_user_id' => $this->config['Mchid'], // opUserId
		);
		$transactionId and $params['transaction_id'] = $transactionId;
		ksort($params);
		$params['sign'] = $this->createSign( $params, $this->config['Key'] );
		$Xml = $this->buildXml($params);
		$resultXml = $this->postSSL( $this->refundUrl, $Xml );
		$result = (array) simplexml_load_string($resultXml, 'SimpleXMLElement', LIBXML_NOCDATA );
		if ( $result['return_code'] == 'SUCCESS' ) {
			if ( $result['result_code'] == 'SUCCESS' ) {
				return array(
					'couponRefundFee' => $result['coupon_refund_fee'], // 现金券退款金额
				);
			} else {
				$this->error = "result_code:".$result['err_code'].":".$result['err_code_des'];
			}
		} else {
			$this->error = "return_msg:".$result['return_msg'].$Xml."----".$resultXml.'---'.$this->getCaName($this->config['SSLCertPath']);
		}
		return false;
	}
	
	/**
	 * 退款查询
	 * @param string $refundId 微信退款单号
	 * @param array $params(*) 
	 * array(
	 * 	  'outRefundNo', // 商户退款单号(*)
	 * 	  'transactionId', // 微信订单号
	 * 	  'orderNo', // 商户系统内部的订单号(*)
	 * )
	 * @return array
	 * $v['outRefundNo'] => array(
	 *   'outRefundNo',// 商户退款单号
	 *   'refundId', // 微信退款单号
	 *   'refundFee', // 退款总金额
	 *   'couponRefundFee',  // 现金券退款金额<=退款金额，退款金额-现金券退款金额为现金
	 *   'refundStatus', // 退款状态(公共属性*)：SUCCES—退款成功 FAIL—退款失败 PROCESSING—退款处理中 NOTSURE—未确定，需要商户原退款单号重新发起CHANGE—转入代发，退款到银行发现用户的卡作废或者冻结了，导致原路退款银行卡失败，资金回流到商户的现金帐号，需要商户人工干预，通过线下或者财付通转账的方式进行退款。	
	 * )
	 */
	public function refundSearch( $refundId = 0, $wparams ) {
		$params = array('appid' => $this->config['appid'],
				'mch_id' => $this->config['Mchid'],
				'nonce_str' => md5(time().rand(9999, 9999)),
				'out_trade_no' => $wparams['orderNo'],
		);
		$refundId and $params['refund_id'] = $refundId;
		$wparams['outRefundNo'] and $params['out_refund_no'] = $wparams['outRefundNo'];
		$wparams['transactionId'] and $params['transaction_id'] = $wparams['transactionId'];
		$params['sign'] = $this->createSign( $params, $this->config['Key'] );
		$Xml = $this->buildXml($params);
		$result = (array) simplexml_load_string(  $this->post( $this->refundSearchUrl, $Xml ), 'SimpleXMLElement', LIBXML_NOCDATA );
		if ( $result['return_code'] == 'SUCCESS' ) {
			if ( $result['result_code'] == 'SUCCESS' ) {
				$refundList = array();
				for($i = 0; $i < $result['refund_count']*1; $i++) {
					$refundList[$result["out_refund_no_{$i}"]] = array(
						'outRefundNo' => $result["out_refund_no_{$i}"], 
						'refundId' => $result["refund_id_{$i}"],
						'refundFee' => $result["refund_fee_{$i}"], 
						'couponRefundFee' => $result["coupon_refund_fee_{$i}"],
						'refundStatus' => $result["refund_status_{$i}"],
					);
				}
				return array(
					'orderNo' => $result['out_trade_no'],
					'list' => $refundList,
				);
			} else {
				$this->error = $result['err_code'].":".$result['err_code_des'];
			}
		} else {
			$this->error = "return_msg:";
		}
		return false;
	}
	
	/**
	 * 签名验证
	 * @see \Utuo\Pay\Pay::verifyNotify()
	 */
	public function verifyNotify( $notify ) {
		if ( $notify['return_code'] == 'SUCCESS' ) {
			if ( $notify['result_code'] == 'SUCCESS' ) {
				$appSignature = $notify['sign'];
				unset( $notify['sign'] );
				if ( $this->createSign($notify, $this->config['Key']) == $appSignature ) {
					return true;
				}
			}
		}
		return false;
	}
	
	
	/**
	 * 构造表单
	 */
	protected function _buildForm($params, $gateway, $method = 'post', $charset = 'utf-8') {
		header("Content-type:text/html;charset={$charset}");
		
		$html = <<<TAG
		<script type="text/javascript">
TAG;
		
		$params['_isReady'] or $html .= <<<TAG
		document.addEventListener('WeixinJSBridgeReady', function onBridgeReady() {
TAG;
	
		$html .= <<<TAG
	WeixinJSBridge.invoke('getBrandWCPayRequest', {
			 "appId" : "{$params['appId']}", // 公众号名称，由商户传入
			 "timeStamp": "{$params['timeStamp']}", // 时间戳，自 1970 年以来的秒数
			 "nonceStr" : "{$params['nonceStr']}", // 随机串
			 "package" : "{$params['package']}",
			 "signType" : "{$params['signType']}", // 微信签名方式:
			 "paySign" : "{$params['paySign']}" // 微信签名
			}, function( res ) {
				if(res.err_msg == "get_brand_wcpay_request:ok" ) {
					// 成功
				} else {
				    var s = "";
					for ( var o in res) {
						s += "&"+o+"="+res[o];					
					}
					alert('支付失败:'+s);
				}
				location.href = "{$this->config['wparam']['backUrl']}";
				// 使用以上方式判断前端返回,微信团队郑重提示：res.err_msg 将在用户支付成功后返回 ok，但并不保证它绝对可靠。
		});
TAG;
		
		$params['_isReady'] or $html .= <<<TAG
		});
TAG;
		
		$html .= <<<TAG
</script>
TAG;
		return $html;
	}
	
	
    /**
     * 创建签名
     * @param type $params
     */
    protected function createSign( $params, $keys ) {
    	ksort($params);
    	reset($params);
    	
    	$arg = '';
    	foreach ($params as $key => $value) {
    		empty($value) or $arg .= "{$key}={$value}&";
    	}
    	return strtoupper(md5($arg . 'key=' . $keys));
    }
    
    /**
     * 创建xml
     * @param unknown $params
     * @return string
     */
    private function buildXml( $params ) {
    	$Xml = "";
    	foreach ( $params as $k => $v ) {
    		if ( !empty($v) ) {
    			$Xml.="<{$k}>{$v}</{$k}>";
    		}
    	}
    	$Xml = "<xml>{$Xml}</xml>";
    	return $Xml;
    }
    
    /**
     * 提交订单
     */
    private function submitOrder( \Utuo\Pay\PayVo $vo ) {
    	$params = array('appid' => $this->config['appid'],
		    		'attach' => serialize($vo->getParam()),
		    		'body' => $vo->getBody() ? : '无',
		    		'mch_id' => $this->config['Mchid'],
		    		'nonce_str' => md5(time().rand(9999, 9999)),
		    		'out_trade_no' => $vo->getOrderNo(),
		    		'total_fee' => floor($vo->getFee() * 100),
		    		'spbill_create_ip' => get_client_ip(), 
    				'notify_url' => $this->config['notify_url'],
    				'trade_type' => 'JSAPI', // JSAPI、NATIVE、APP
    				'openid' => session(session("openIdKey")),
    			);
    	$params['sign'] = $this->createSign( $params, $this->config['Key'] );
		$Xml = $this->buildXml($params);
    	$result = $this->post( $this->prePay, $Xml );
//     	if ( $_REQUEST['sid'] == 251 ) {
//     		dump($Xml);
//     		dump($result);
//     	}
    	return (array) simplexml_load_string( $result, 'SimpleXMLElement', LIBXML_NOCDATA );
    }
    
    /**
     * 提交数据
     * @param unknown $url
     * @param unknown $params
     * @return mixed
     */
    private function post( $url, $params ) {
    	$ch = curl_init();
    	curl_setopt ( $ch, CURLOPT_URL, $url );
    	curl_setopt ( $ch, CURLOPT_POST, 1 );
    	curl_setopt ( $ch, CURLOPT_HEADER, 0 );
    	curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 );
    	curl_setopt ( $ch, CURLOPT_POSTFIELDS, $params );
    	$return = curl_exec ( $ch );
    	curl_close ( $ch );
    	return $return;
    }
    
    /**
     * 带入证书的post提交
     * @param unknown $url
     * @param unknown $params
     * @return mixed
     */
    private function postSSL( $url, $params ) {
    	$ch = curl_init();
    	//超时时间
    	curl_setopt($ch,CURLOPT_TIMEOUT, 30);
    	curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 );
    	curl_setopt ( $ch, CURLOPT_URL, $url );
    	curl_setopt ( $ch, CURLOPT_HEADER, 0 );
    	//post提交方式
    	curl_setopt ( $ch, CURLOPT_POST, 1 );
    	curl_setopt ( $ch, CURLOPT_POSTFIELDS, $params );
    	//设置证书
    	curl_setopt	($ch, CURLOPT_SSL_VERIFYPEER, false); // 是否检测服务器的证书是否由正规浏览器认证过的授权CA颁发的
    	curl_setopt	($ch, CURLOPT_SSL_VERIFYHOST, false); // CURLOPT_SSL_VERIFYHOST：是否检测服务器的域名与证书上的是否一致
    	curl_setopt	($ch, CURLOPT_SSLCERTTYPE, "PEM"); // 证书类型，"PEM" (default), "DER", and"ENG".
    	curl_setopt($ch, CURLOPT_SSLCERT, $this->getCaName($this->config['SSLCertPath'])); // 证书存放路径
    	curl_setopt	($ch, CURLOPT_SSLKEYTYPE, "PEM"); // 私钥类型，"PEM" (default), "DER", and"ENG".
    	curl_setopt($ch, CURLOPT_SSLKEY, $this->getCaName($this->config['SSLKeyPath'])); // 私钥存放路径
    	//curl_setopt($ch, CURLOPT_CAINFO, $this->config['SSLRootCaPath']); // 根证书
    	
    	$return = curl_exec ( $ch );
    	curl_close ( $ch );
    	return $return;    	
    }
	
    /**
     * 获得证书路径
     * @param unknown $fileName
     * @return string
     */
    private function getCaName( $fileName ) {
        $dirname = dirname($fileName);
        return getcwd().'/ca/'."{$this->config['Mchid']}".substr($fileName, strlen($dirname));
    }
}